// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdlib.h>

#include <ddk/debug.h>
#include <dev/pci/designware/atu-cfg.h>
#include <hw/reg.h>

#include "aml-pcie-device.h"
#include "aml-pcie-regs.h"
#include "aml-pcie.h"

namespace pcie {
namespace aml {

void AmlPcie::AssertReset(const uint32_t mask) { rst_.ClearBits32(mask, 0); }

void AmlPcie::ClearReset(const uint32_t mask) { rst_.SetBits32(mask, 0); }

void AmlPcie::RmwCtrlSts(const uint32_t size, const uint32_t shift, const uint32_t mask) {
  uint32_t regval;
  switch (size) {
    case 128:
      regval = 0;
      break;
    case 256:
      regval = 1;
      break;
    case 512:
      regval = 2;
      break;
    case 1024:
      regval = 3;
      break;
    case 2048:
      regval = 4;
      break;
    case 4096:
      regval = 5;
      break;
    default:
      regval = 1;
  }

  dbi_.ClearBits32(mask << shift, PCIE_CTRL_STS_OFF);
  dbi_.SetBits32(regval << shift, PCIE_CTRL_STS_OFF);
}

void AmlPcie::PcieInit() {
  cfg_.SetBits32(APP_LTSSM_ENABLE, 0);

  dbi_.SetBits32(PLC_FAST_LINK_MODE, PORT_LINK_CTRL_OFF);

  dbi_.ClearBits32(PLC_LINK_CAPABLE_MASK, PORT_LINK_CTRL_OFF);

  dbi_.SetBits32(PLC_LINK_CAPABLE_X1, PORT_LINK_CTRL_OFF);

  dbi_.ClearBits32(G2_CTRL_NUM_OF_LANES_MASK, GEN2_CTRL_OFF);

  dbi_.SetBits32(G2_CTRL_NO_OF_LANES(1), GEN2_CTRL_OFF);

  dbi_.SetBits32(G2_CTRL_DIRECT_SPEED_CHANGE, GEN2_CTRL_OFF);
}

void AmlPcie::SetMaxPayload(const uint32_t size) {
  const uint32_t kShift = 5;
  const uint32_t kMask = 0x7;
  RmwCtrlSts(size, kShift, kMask);
}

void AmlPcie::SetMaxReadRequest(const uint32_t size) {
  const uint32_t kShift = 12;
  const uint32_t kMask = 0x7;
  RmwCtrlSts(size, kShift, kMask);
}

void AmlPcie::EnableMemorySpace() {
  // Cause the root port to handle transactions.
  constexpr uint32_t bits = (PCIE_TYPE1_STS_CMD_IO_ENABLE | PCIE_TYPE1_STS_CMD_MEM_SPACE_ENABLE |
                             PCIE_TYPE1_STS_CMD_BUS_MASTER_ENABLE);
  dbi_.SetBits32(bits, PCIE_TYPE1_STS_CMD_OFF);
}

bool AmlPcie::IsLinkUp() {
  uint32_t val = cfg_.Read32(PCIE_CFG_STATUS12);

  return (val & PCIE_CFG12_SMLH_UP) && (val & PCIE_CFG12_RDLH_UP) &&
         ((val & PCIE_CFG12_LTSSM_MASK) == PCIE_CFG12_LTSSM_UP);
}

zx_status_t AmlPcie::AwaitLinkUp() {
  for (unsigned int i = 0; i < 500000; i++) {
    if (IsLinkUp()) {
      zxlogf(SPEW, "aml_dw: pcie link up okay\n");
      return ZX_OK;
    }
    zx_nanosleep(zx_deadline_after(ZX_USEC(10)));
  }
  return ZX_ERR_TIMED_OUT;
}

void AmlPcie::ConfigureRootBridge(const iatu_translation_entry_t* mem) {
  // PCIe Type 1 header Bus Register (offset 0x18 into the Ecam)
  auto reg = PciBusReg::Get().ReadFrom(&dbi_);

  // The Upstream Bus for the root bridge is Bus 0
  reg.set_primary_bus(0x0);

  // The Downstream bus for the root bridge is Bus 1
  reg.set_secondary_bus(0x1);

  // This bridge will also claim all transactions for any other bus IDs on
  // this bus.
  reg.set_subordinate_bus(0xff);

  reg.WriteTo(&dbi_);

  // Zero out the BARs for the Root bridge because the DW root bridge doesn't
  // need them.
  dbi_.Write32(0, PCI_TYPE1_BAR0);
  dbi_.Write32(0, PCI_TYPE1_BAR1);

  const uint32_t kRevisionMask = 0x000000ff;
  const uint32_t kDeviceBridge = 0x600;
  const uint32_t kDevicePciBridge = 0x004;
  const uint32_t kDeviceShift = 8;

  // This device improperly reports the class of the root bridge so we need
  // to fill in the correct value.
  uint32_t val = dbi_.Read32(PCI_CLASSREV);
  val &= kRevisionMask;
  val |= (kDeviceBridge | kDevicePciBridge) << kDeviceShift;
  dbi_.Write32(val, PCI_CLASSREV);

  // Set the Base and limit registers for this root bridge.
  // On x86 we rely on the BIOS to do this for us, but on arm we must size our
  // own bridges. Normally we'd scan the bus and perform this dynamically but
  // our bus driver doesn't handle this for now. Fortunately we already know
  // the exact topology of our bus so sizing the bridges is not terribly
  // difficult.
  // These are both hacks for the AMLogic implementation of this driver.
  // Ideally we should be pulling these out of the iATU config.
  dbi_.Write32(0x000000f0, PCI_IO_BASE_LIMIT);
  dbi_.Write32(0xf9f0f9e0, PCI_MEM_BASE_LIMIT);
}

zx_status_t AmlPcie::EstablishLink(const iatu_translation_entry_t* cfg,
                                   const iatu_translation_entry_t* io,
                                   const iatu_translation_entry_t* mem) {
  zx_status_t st;

  PcieInit();

  SetMaxPayload(256);

  SetMaxReadRequest(256);

  st = SetupRootComplex(cfg, io, mem);
  if (st != ZX_OK) {
    zxlogf(ERROR, "aml_pcie: failed to setup root complex, st = %d\n", st);
    return st;
  }

  EnableMemorySpace();

  st = AwaitLinkUp();
  if (st != ZX_OK) {
    zxlogf(ERROR, "aml_pcie: failed awaiting link up, st = %d\n", st);
    return st;
  }

  ConfigureRootBridge(mem);

  return ZX_OK;
}

}  // namespace aml
}  // namespace pcie
